//
//  main.m
//  block学习
//
//  Created by Hello Cai on 2019/3/26.
//  Copyright © 2019 Hello Cai. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "TestObj.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        //以下内容来源于：https://www.jianshu.com/p/fe772a3536ca
//-----------------------------------------
//-----------------------------------------
#if 0
        /*---打印
         before block retainCount:1
         after block retainCount:3
         等号右边引出某个block，内部访问了obj，obj retain一次（block在栈中，强引用obj）。
         将该block赋值给等号左边的block变量，obj再retain一次（此时堆中拷贝了栈的block，堆的block也对obj做了强引用）
         */
        void (^myblock)(void);
        TestObj *obj = [[TestObj alloc] init];
        NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
        myblock = ^(){
            NSLog(@"TestObj对象地址:%@",obj);
        };
        NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
        
        /*
         =============关于栈和堆==============
         打印：
         __NSStackBlock__,0x7fffa02da070    【即未赋值前，代码块的内容在栈中】
         __NSMallocBlock__,0x7fffa02da170   【即赋值给变量后，相当于拷贝了栈中的内容至堆中】
         */
        NSLog(@"%@,%p",
              [^(){NSLog(@"TestObj对象地址:%@",obj);} class],
              [^(){NSLog(@"TestObj对象地址:%@",obj);} class]);  //block在栈中
        NSLog(@"%@,%p",[myblock class],[myblock class]);          //block在堆中
#endif
        
#if 0   //【solve】
        /*---打印
         before block retainCount:1
         after block retainCount:1
         满足我们希望的情况
         */
        void (^myblock)(void);
        TestObj *obj = [[TestObj alloc] init];
        __weak TestObj *weakObj = obj;
        NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
        myblock = ^(){
            NSLog(@"TestObj对象地址:%@",weakObj);
        };
        NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);

#endif

        
//-----------------------------------------*****
//-----------------------------------------*****
#if 0   //关于强引用和弱引用理解
        __weak NSObject *temp = nil;
        {
            NSObject *object = [[NSObject alloc] init];
            temp = object;
            NSLog(@"object:%@",object);
        }
        //以下打印为null。原因：object默认是强引用，但离开{}的作用域后，object持有的对象被释放了，temp是弱引用，不持有对象，当对象释放后，temp自动赋值为nil
        NSLog(@"temp:%@",temp);
#endif
        
#if 0
        __weak TestObj *tob = nil;
        {                                         //离开作用域，局部变量清空
            void (^myblock)(void);                  //离开作用域，block不持有
            TestObj *obj = [[TestObj alloc] init];//离开作用域，obj不持有
            tob = obj;  //弱引用=强引用后，weak的retainCount+1，strong的retainCount不变
            NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
            myblock = ^(){
                NSLog(@"TestObj对象地址:%@",obj);
            };
            NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);//打印3
            NSLog(@"later block retainCount:%zd",[tob arcDebugRetainCount]);//打印4
            myblock();
        }   //离开作用域，局部变量清空（即obj、栈block、堆myblock不持有对象，断开强引用，对象完全释放，弱引用tob自动赋值为nil）
        NSLog(@"finally block retainCount:%zd",[tob arcDebugRetainCount]);//打印0
        NSLog(@"tob对象地址:%@",tob);       //打印null
#endif
        
#if 0
        //与上面相比，将block放在{}外面，block作用域更大
        void (^myblock)(void);
        __weak TestObj *tob = nil;
        {
            TestObj *obj = [[TestObj alloc] init];//obj强引用一次。离开作用域，obj不持有
            tob = obj;  //弱引用=强引用后，weak的retainCount+1，strong的retainCount不变
            NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
            myblock = ^(){//代码块内部访问obj，栈block强引用一次。赋值给变量myblock后，堆block强引用一次
                NSLog(@"TestObj对象地址:%@",obj);
            };
            NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);//打印3
            NSLog(@"later block retainCount:%zd",[tob arcDebugRetainCount]);//打印4（多了tob的）
            myblock();
        }   //离开作用域，局部变量清空（即obj、栈block不持有对象，断开强引用）
        //打印2(一个是tob，一个是myblock)
        NSLog(@"finally block retainCount:%zd",[tob arcDebugRetainCount]);
        NSLog(@"tob对象地址:%@",tob);
#endif
        
        
//-----------------------------------------
//-----------------------------------------
#if 0
        /*---打印
         before block retainCount:1
         after block retainCount:1
         TestObj对象地址:<TestObj: 0x100500a90>
         耗时的任务 结束 TestObj对象地址:(null)    【对象销毁了】
         看看1:(null)
         耗时的任务 结束 TestObj对象地址:(null)
         看看2:(null)
         
         分析总结：单个线程,执行Block的时候对象还没有被置为nil,直到Block被执行完毕,这个对象都不会被释放。
         但是在多线程的情况下,就可能造成在执行上半部分代码的时候,对象还在,而在执行下半部代码的时候对象已经被释放,如下面这个例子：
         */
        
        //增加个弱引用t0（弱引用不持有对象，不改变retainCount），用来观察出了作用域后obj是否被销毁了
        __weak TestObj *t0 = nil;
        {
            void (^myblock)(void);
            TestObj *obj = [[TestObj alloc] init];
            __weak TestObj *weakObj = obj;
            NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
            t0 = obj;
            myblock = ^(){
                NSLog(@"TestObj对象地址:%@",weakObj);
                //异步
                dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
                    for (int i = 0; i < 1000000; i++) {
                        // 模拟一个耗时的任务
                    }
                    NSLog(@"耗时的任务 结束 TestObj对象地址:%@",weakObj);//打印null，对象出了作用域被销毁
                });
            };
            NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
            myblock();
        }  //一般情况下出了作用域，obj会被销毁，block内部处理完耗时任务后回调，无法获取操作对象。解决方式请看下一个案例
        NSLog(@"看看1:%@",t0); //输出为null，对象已被销毁
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"看看2:%@",t0); //前面都销毁了，这里自然也是null
#endif
        
#if 1   //【solve】
        /**打印正常，对象不会销毁
         before block retainCount:1
         after block retainCount:1
         TestObj对象地址:<TestObj: 0x100539ee0>
         看看1:<TestObj: 0x100539ee0>
         耗时的任务 结束 TestObj对象地址:<TestObj: 0x100539ee0>
         看看2:(null)
         */
        __weak TestObj *t0 = nil;   //观察对象是否销毁
        {
            void (^myblock)(void);
            TestObj *obj = [[TestObj alloc] init];
            __weak TestObj *weakObj = obj;
            NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
            t0 = obj;
            myblock = ^(){
                __strong  TestObj *strongObj = weakObj;
                if(! strongObj) return;
                NSLog(@"TestObj对象地址:%@",weakObj);
                //异步
                dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
                    for (int i = 0; i < 1000000; i++) {
                        // 模拟一个耗时的任务
                    }
                    NSLog(@"耗时的任务 结束 TestObj对象地址:%@",strongObj);
                });
            };
            NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
            myblock();
        }   //离开作用域，局部变量清空（即obj不持有对象，断开强引用，但strongObj特殊，异步中有用到strongObj，短暂持有对象）
        NSLog(@"看看1:%@",t0);  //正常输出对象（出了作用域对象没有被销毁，原因是异步用到strongObj，strongObj会暂时性持有对象，直到异步执行完，strongObj不再需要，才释放对象）
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"看看2:%@",t0);  //输出为null，因为异步任务已经执行完了
#endif
    }
    return 0;
}
